package logic

import (
	rediscache "ahutoj/web/cache/redis"
	"ahutoj/web/dao"
	"ahutoj/web/io/constanct"
	"ahutoj/web/io/request"
	"ahutoj/web/io/response"
	"ahutoj/web/middlewares"
	"ahutoj/web/models"
	"ahutoj/web/utils"
	"errors"
	"fmt"
	"sort"
	"strings"
	"time"

	"github.com/gin-gonic/gin"
)

func AddContest(ctx *gin.Context, req *request.AddContestReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	contest := dao.Contest{
		UID:         middlewares.GetUid(ctx),
		Title:       req.Title,
		Description: req.Description,
		Begin_time:  req.Begin_time,
		End_time:    req.End_time,
		Ctype:       req.Ctype,
		Ispublic:    req.Ispublic,
		Problems:    req.Problems,
		Pass:        req.Pass,
	}
	err := models.AddContestToDb(ctx, contest)
	if err != nil {
		logger.Errorf("call AddContestToDb failed, err=%s", err.Error())
		return response.CreateResponse(constanct.CONTEST_ADD_FAILED), err
	}
	contest.CID, err = models.GetCurrentCID(ctx, contest)
	if err != nil {
		logger.Errorf("call GetCurrentCID failed, err=%s", err.Error())
		return response.CreateResponse(constanct.CONTEST_ADD_FAILED), err
	}
	err = models.AddConproblems(ctx, req.Problems, contest.CID)
	if err != nil {
		logger.Errorf("call AddConproblems failed,req:%v, err=%s", req.Problems, err.Error())
		return response.CreateResponse(constanct.CONTEST_ADD_FAILED), err
	}
	return response.AddContestResp{
		Response: response.CreateResponse(constanct.SuccessCode),
		CID:      contest.CID,
	}, nil
}

func EditContest(ctx *gin.Context, req *request.EditContestReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	contest := dao.Contest{
		CID:         req.CID,
		UID:         req.UID,
		Title:       req.Title,
		Description: req.Description,
		Begin_time:  req.Begin_time,
		End_time:    req.End_time,
		Ctype:       req.Ctype,
		Ispublic:    req.Ispublic,
		Problems:    req.Problems,
		Pass:        req.Pass,
	}
	err := models.SaveContestDB(ctx, contest)
	if err != nil {
		logger.Errorf("call SaveContestDB failed, err=%s", err.Error())
		return response.CreateResponse(constanct.CONTEST_EDIT_FAILED), err
	}
	err = models.AddConproblems(ctx, req.Problems, contest.CID)
	if err != nil {
		logger.Errorf("call AddConproblems failed, err=%s", err.Error())
		return response.CreateResponse(constanct.CONTEST_EDIT_FAILED), err
	}
	return response.CreateResponse(constanct.SuccessCode), nil
}

func DeleteContest(ctx *gin.Context, req *request.DeleteContestReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	err := models.DeleteContestDB(ctx, req.CIDs)
	if err != nil {
		logger.Errorf("call DeleteContestDB failed, err=%s", err.Error())
		return response.CreateResponse(constanct.CONTEST_DELETE_FAILED), err
	}
	return response.CreateResponse(constanct.SuccessCode), nil
}

func GetListContest(ctx *gin.Context, req *request.ContestListReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	offset, limit := utils.GetPageInfo(req.Page, req.Limit)
	var (
		ContestList []dao.Contest
		err         error
		counts      int64
	)
	ContestList, err = models.GetContestListFromDb(ctx, offset, limit)
	if err != nil {
		logger.Errorf("call GetContestListFromDb failed,err=%s", err.Error())
		return response.CreateResponse(constanct.CONTEST_LIST_FAILED), err
	}
	respData := make([]response.ContestListItem, len(ContestList))
	for i, contest := range ContestList {
		respData[i] = response.ContestListItem{
			CID:       contest.CID,
			UID:       contest.UID,
			Title:     contest.Title,
			BeginTime: contest.Begin_time,
			EndTime:   contest.End_time,
			Type:      contest.Ctype,
			Ispublic:  contest.Ispublic,
		}
	}
	counts, _ = models.GetContestCountFromDB(ctx)

	sort.Slice(respData, func(i, j int) bool {
		if respData[i].EndTime == respData[j].EndTime {
			if respData[i].BeginTime == respData[j].BeginTime {
				return respData[i].CID < respData[j].CID
			}
			return respData[i].BeginTime < respData[j].BeginTime
		}
		return respData[i].EndTime > respData[j].EndTime
	})

	return response.ContestListResp{
		Response: response.CreateResponse(constanct.SuccessCode),
		Size:     counts,
		Data:     respData,
	}, nil
}

func GetBaseContest(ctx *gin.Context, req *request.GetBaseContestReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	contest, err := models.GetBaseContestFromDB(ctx, req.CID)
	if err != nil {
		logger.Errorf("call GetContestFromDB failed, CID=%d, err=%s", req.CID, err.Error())
		return response.CreateResponse(constanct.CONTEST_GET_FAILED), err
	}
	return response.GetBaseContestResp{
		Response:    response.CreateResponse(constanct.SuccessCode),
		CID:         contest.CID,
		UID:         contest.UID,
		Title:       contest.Title,
		Description: contest.Description,
		Begin_time:  contest.Begin_time,
		End_time:    contest.End_time,
		Ctype:       contest.Ctype,
		Ispublic:    contest.Ispublic,
	}, nil
}

func GetContest(ctx *gin.Context, req *request.GetContestReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	contest, err := models.GetContestFromDB(ctx, req.CID)

	if err != nil {
		logger.Errorf("call GetContestFromDB failed, CID=%d, err=%s", req.CID, err.Error())
		return response.CreateResponse(constanct.CONTEST_GET_FAILED), err
	}
	if contest.CID != req.CID {
		logger.Errorf("contest not exites req=%+v", utils.Sdump(req))
		return response.CreateResponse(constanct.CONTEST_GET_CIDNotExistCode), nil
	}
	uid := middlewares.GetUid(ctx)
	isAdmin := false
	if uid != "" {
		isAdmin = models.CheckUserPermission(ctx, uid, models.Contest_creator)
	}
	// 不是管理员且 竞赛未开始
	if !isAdmin && contest.Begin_time > utils.GetNow() {
		logger.Errorf("contest not begin")
		return response.CreateResponse(constanct.CONTEST_GET_NotBegin), nil
	}
	//不是管理员的情况下 竞赛私有  并且 （没有密码，或者密码错误）
	if !isAdmin && contest.Ispublic != 1 && (req.Pass == "" || req.Pass != contest.Pass) {
		logger.Errorf("contest pass word error req=%+v", utils.Sdump(req))
		return response.CreateResponse(constanct.CONTEST_GET_CIDPassWordErrorCode), nil
	}
	conPros, err := models.GetConProblemFromDB(ctx, contest)
	if err != nil {
		logger.Errorf("call GetConProblemFromDB failed, CID=%d, err=%s", req.CID, err.Error())
		return response.CreateResponse(constanct.CONTEST_GET_FAILED), err
	}
	PIDs := strings.Split(contest.Problems, ",")
	Data := make(map[string]*response.ConProItem)
	for _, conPro := range conPros {
		temp := &response.ConProItem{
			PID:          conPro.PID,
			Ptitle:       conPro.Ptitle,
			Submit_num:   conPro.Submited,
			Ac_num:       conPro.Solved,
			Status:       models.MyStatus(ctx, uid, conPro.PID, contest.CID),
			BalloonColor: conPro.BalloonColor,
		}
		Data[conPro.PID] = temp
	}
	respData := make([]*response.ConProItem, 0)
	for _, PID := range PIDs {
		if _, ok := Data[PID]; ok {
			respData = append(respData, Data[PID]) //按照比赛题目的顺序返回
		}
	}
	return response.GetContestResp{
		Response:    response.CreateResponse(constanct.SuccessCode),
		CID:         contest.CID,
		UID:         contest.UID,
		Title:       contest.Title,
		Description: contest.Description,
		Begin_time:  contest.Begin_time,
		End_time:    contest.End_time,
		Ctype:       contest.Ctype,
		Ispublic:    contest.Ispublic,
		Size:        int64(len(conPros)),
		Problems:    contest.Problems,
		ProblemData: respData,
	}, nil
}
func CloneContest(ctx *gin.Context, req *request.CloneContestReq) (interface{}, error) {
	uid := middlewares.GetUid(ctx)
	if uid != req.UID {
		return response.CreateResponse(constanct.ADMIN_ADD_UIDEmpty), nil
	}
	contest, err := models.GetContestFromDB(ctx, req.CID)
	if err != nil {
		return response.CreateResponse(constanct.ServerErrorCode), err
	}

	list := &dao.List{
		UID:         uid,
		StartTime:   utils.GetNow(),
		Problems:    contest.Problems,
		Description: contest.Description + "(By Clone)",
		Title:       contest.Title + "(By Clone)",
		FromLID:     0,
	}
	err = models.CreateList(ctx, list)
	if err != nil {
		//日志报错
		utils.GetLogInstance().Errorf("call CreateList failed,err=%s", err.Error())
		return response.CreateResponse(constanct.TRAIN_ADD_FAILED), err
	}
	list.LID, err = models.GetCurrentLID(ctx, list)
	if err != nil {
		//日志报错
		utils.GetLogInstance().Errorf("call GetLID in CreateList failed,err=%s", err.Error())
		return response.CreateResponse(constanct.TRAIN_ADD_FAILED), err
	}
	problems, _ := models.GetContestProblem(ctx, req.CID)
	// 添加提单题目
	for _, PID := range problems {
		listproblem := dao.ListProblem{
			LID: list.LID,
			PID: PID.PID,
		}
		err = models.CreateListProblem(ctx, &listproblem)
		if err != nil {
			//日志报错
			utils.GetLogInstance().Errorf("call CreateListProblem failed,err=%s", err.Error())
			return response.CreateResponse(constanct.TRAIN_ADD_FAILED), err
		}
	}
	return response.CloneContestResp{
		Response: response.CreateResponse(constanct.SuccessCode),
		LID:      list.LID,
	}, nil
}
func initContestRankItem(rank *response.RankItemWithAcm, submit *dao.Submit, problemSize int) {
	rank.Uname = submit.User.Uname
	rank.UserID = submit.UID
	rank.AllSubmit = 0
	rank.Problems = make([]response.ProblemItemWithACM, problemSize)
	for idx := range rank.Problems {
		problem := &rank.Problems[idx]
		problem.PID = ""
		problem.Time = 0
		problem.Status = constanct.OJ_NULL
	}
}
func initRankItemWithIOI(rank *response.RankItemWithIOI, submit *dao.Submit, problemSize int) {
	rank.Uname = submit.User.Uname
	rank.UserID = submit.UID
	rank.Problems = make([]response.ProblemItemWithIOI, problemSize)
	for idx := range rank.Problems {
		problem := &rank.Problems[idx]
		problem.PID = ""
		problem.Time = 0
		problem.PassSample = 0
		problem.SampleNmuber = 0
		problem.Submited = false
	}
}

const (
	ACM int = 1
	IOI int = 2
	OI  int = 3
)

type ContestRankFunc func(ctx *gin.Context, contest dao.Contest, current int64) (interface{}, error)

var ContestRankFuncMap = map[int]ContestRankFunc{
	ACM: GetRankContestWithACM,
	IOI: GetRankContestWithIOI,
}

func GetRankContest(ctx *gin.Context, req *request.GetContestRankReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	contest, err := models.GetContestFromDB(ctx, req.CID)
	if err != nil {
		logger.Errorf("call GetContestFromDB Failed, CID=%d, err=%s", req.CID, err.Error())
		return response.CreateResponse(constanct.CONTEST_RANK_FAILED), err
	}
	if contest.CID != req.CID {
		return response.CreateResponseStr(constanct.CONTEST_RANK_FAILED, "竞赛不存在", response.WARNING), nil
	}
	current := utils.GetNow()
	if contest.Begin_time > current {
		return response.CreateResponseStr(constanct.CONTEST_RANK_FAILED, "竞赛未开始", response.WARNING), nil
	}
	contestRankFunc, ok := ContestRankFuncMap[contest.Ctype]
	if !ok {
		return response.CreateResponseStr(constanct.CONTEST_RANK_FAILED, "不支持的竞赛类型", response.WARNING), nil
	}
	return contestRankFunc(ctx, *contest, current)
	//优化：Singleflight 解决短时间重复并发请求，避免redis大key
	//MapReduce 处理数据库返回数据，避免遍历？
	//索引优化有多少提升？
}

type RankWsFunc func(ctx *gin.Context, idx int, submit *dao.Submit, problemIdxMap map[string]int, userMap map[string]int, contest *dao.Contest, rawRank any) error

// XCPC 赛制 -> 典型：ICPC
// 多次提交机会，即时反馈结果，错误罚时20min，全部正确才得分，最后1h封榜
func GetRankContestWithACM(ctx *gin.Context, contest dao.Contest, current int64) (interface{}, error) {
	logger := utils.GetLogInstance()
	// problem对应的一个下标 PID ---> idx;
	problemIdxMap := make(map[string]int)
	PIDs := strings.Split(contest.Problems, ",")
	for idx, PID := range PIDs {
		problemIdxMap[PID] = idx
	}
	fb := int64(utils.GetConfInstance().Terminal*(float64(contest.End_time)-float64(contest.Begin_time)) + float64(contest.Begin_time))
	if current-contest.End_time > int64(utils.GetConfInstance().OpenTime*float64(time.Hour)) {
		fb = int64(float64(contest.End_time))
	}
	// logger.Debugf("oepnTime:%v,fb:%v",utils.GetConfInstance().OpenTime,fb)
	//封榜时间
	//todo :改为分组聚集搭配redis缓存策略
	submits, err := models.GetSubmitByCIDFromDBWithUname(ctx, contest.CID, fb) //获取使用这个竞赛的所有提交
	// sort.Slice(submits, func(i, j int) bool {
	// 	return submits[i].SubmitTime < submits[j].SubmitTime
	// })
	if err != nil {
		logger.Errorf("call GetContestFromDB Failed, CID=%d, err=%s", contest.CID, err.Error())
		return response.CreateResponse(constanct.CONTEST_RANK_FAILED), err
	}
	userMap := make(map[string]int)
	ranks := make(response.RankItemsWithAcm, 0)
	idx := 0
	for _, submit := range submits {
		RankWithACM(ctx, &idx, &submit, problemIdxMap, userMap, &contest, &ranks)
	}
	return &response.ContestRankRespWithAcm{
		Response:      response.CreateResponse(constanct.SuccessCode),
		Size:          len(ranks),
		Data:          ranks,
		ProblemIdxMap: problemIdxMap,
		UserMap:       userMap,
		Idx:           idx,
	}, nil
}

func RankWithACM(ctx *gin.Context, idx *int, submit *dao.Submit, problemIdxMap map[string]int, userMap map[string]int, contest *dao.Contest, ranks *response.RankItemsWithAcm) error {
	rid, ok := userMap[submit.UID]
	if !ok {
		// 添加一个排行榜用户
		rid = *idx
		*idx += 1
		userMap[submit.UID] = rid
		*ranks = append(*ranks, response.RankItemWithAcm{})
		initContestRankItem(&(*ranks)[rid], submit, len(problemIdxMap))
	}
	// 获取用户的排行信息
	rank := &(*ranks)[rid]
	problem := &rank.Problems[problemIdxMap[submit.PID]]
	problem.PID = submit.PID
	if problem.Status == constanct.OJ_AC { //之前已经AC了，状态不会变
		return nil
	}
	if submit.Result == constanct.OJ_AC {
		rank.PenaltySubmit += problem.SubmitNumber - problem.CENumber - problem.JudgeErrorNumber
		rank.ACNumber++
	} //只有在新产生一道题AC的时候，关于一道题目的之前罚时次数才会增加
	problem.Status = submit.Result
	problem.Time = submit.SubmitTime - contest.Begin_time
	rank.AllSubmit++
	problem.SubmitNumber++
	if submit.Result == constanct.OJ_DENIAL || submit.Result == constanct.OJ_TIMEOUT ||
		submit.Result == constanct.OJ_FAILED || submit.Result == constanct.OJ_JUDGE {
		problem.JudgeErrorNumber++
	}
	if submit.Result == constanct.OJ_CE {
		problem.CENumber++
	}
	return nil
}

// IOI 赛制 -> 典型天梯赛
// 多次提交机会，即时反馈结果，错误无惩罚，按点得分
func GetRankContestWithIOI(ctx *gin.Context, contest dao.Contest, current int64) (interface{}, error) {
	logger := utils.GetLogInstance()
	problemIdxMap := make(map[string]int, 0)
	PIDs := strings.Split(contest.Problems, ",")
	for idx, PID := range PIDs {
		problemIdxMap[PID] = idx
	}
	fb := int64((float64(contest.End_time) - float64(contest.Begin_time)) + float64(contest.Begin_time))
	// if current-contest.End_time > int64(utils.GetConfInstance().OpenTime*float64(time.Hour)) {
	// 	fb = 0
	// }
	// logger.Debugf("oepnTime:%v,fb:%v",utils.GetConfInstance().OpenTime,fb)
	submits, err := models.GetSubmitByCIDFromDBWithUname(ctx, contest.CID, fb) //获取使用这个竞赛的所有提交
	// sort.Slice(submits, func(i, j int) bool {
	// 	return submits[i].SubmitTime < submits[j].SubmitTime
	// })
	if err != nil {
		logger.Errorf("call GetContestFromDB Failed, CID=%d, err=%s", contest.CID, err.Error())
		return response.CreateResponse(constanct.CONTEST_RANK_FAILED), err
	}
	userMap := make(map[string]int, 0)
	ranks := make(response.RankItemsWithIOI, 0)
	idx := 0
	for _, submit := range submits {
		RankWithIOI(ctx, &idx, &submit, problemIdxMap, userMap, &contest, &ranks)
	}
	return &response.ConntestRankRespWithIOI{
		Response:      response.CreateResponse(constanct.SuccessCode),
		Size:          len(ranks),
		Data:          ranks,
		ProblemIdxMap: problemIdxMap,
		UserMap:       userMap,
		Idx:           idx,
	}, nil
}
func RankWithIOI(ctx *gin.Context, idx *int, submit *dao.Submit, problemIdxMap map[string]int, userMap map[string]int, contest *dao.Contest, ranks *response.RankItemsWithIOI) error { //其实这里ranks可以用any，然后整个Map，但是害怕性能损耗~
	rid, ok := userMap[submit.UID]
	if !ok {
		// 添加一个排行榜用户
		rid = *idx
		*idx += 1
		userMap[submit.UID] = rid
		*ranks = append(*ranks, response.RankItemWithIOI{})
		initRankItemWithIOI(&(*ranks)[rid], submit, len(contest.Problems))
	}
	// 获取用户的排行信息
	rank := &(*ranks)[rid]
	problem := &rank.Problems[problemIdxMap[submit.PID]]
	problem.PID = submit.PID
	problem.Submited = true
	problem.SampleNmuber = submit.SampleNmuber
	if problem.PassSample <= int64(submit.PassSample) {
		problem.PassSample = int64(submit.PassSample)
		problem.Time = utils.MinInt64(problem.Time, submit.Usetime)
		if submit.Result == constanct.OJ_AC {
			rank.ACNumber++
		}
	}
	return nil
}

func UpdateConproColor(ctx *gin.Context, req *request.UpdateConproColorReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	err := models.UpdateConproColor(ctx, req.CID, req.PID, req.Color)
	if err != nil {
		logger.Errorf("call UpdateConproColor Failed, CID=%d, err=%s", req.CID, err.Error())
		return response.CreateResponse(constanct.CONPRO_COLOR_UPDATE_FAILED), err
	}
	return response.UpdateConproColorResp{
		Response: response.CreateResponse(constanct.SuccessCode),
		Color:    req.Color,
	}, nil
}
func UpdateConproScore(ctx *gin.Context, req *request.UpdateConproScoreReq) (interface{}, error) {
	logger := utils.GetLogInstance()
	err := models.UpdateConproScore(ctx, req.CID, req.PID, req.Score)
	if err != nil {
		logger.Errorf("call UpdateConproScore Failed, CID=%d, err=%s", req.CID, err.Error())
		return response.CreateResponse(constanct.CONPRO_COLOR_UPDATE_FAILED), err
	}
	return response.UpdateConproScoreResp{
		Response: response.CreateResponse(constanct.SuccessCode),
		Score:    req.Score,
	}, nil
}
func GetWsRankClient(ctx *gin.Context, req *request.GetWsRankClientReq) error {
	logger := utils.GetLogInstance()
	//todo websocket协议升级
	UID := middlewares.GetUid(ctx)
	key := UID
	conn, ok := middlewares.GetConn(key)
	if !ok {
		logger.Errorf("GetConn Failed, key=%s", key)
		return errors.New("GetConn Failed")
	}
	contest, err := models.GetContestFromDB(ctx, req.CID)
	if err != nil {
		logger.Errorf("call GetContestFromDB Failed, CID=%d, err=%s", req.CID, err.Error())
		return err
	}
	current := utils.GetNow()
	if contest.Begin_time > current {
		logger.Infof("contest not begin req=%+v", utils.Sdump(req))
		return errors.New("contest not begin")
	}
	contestRankFunc, ok := ContestRankFuncMap[contest.Ctype]
	if !ok {
		logger.Errorf("contestRankFunc not exites req=%+v", utils.Sdump(req))
		return errors.New("contestRankFunc not exites")
	}
	rawRank, err := contestRankFunc(ctx, *contest, current)
	if err != nil {
		logger.Errorf("call contestRankFunc Failed, CID=%d, err=%s", req.CID, err.Error())
		return err
	}
	err = conn.WriteJSON(rawRank) //首先返回一个全量排行榜
	if err != nil {
		logger.Errorf("call conn.WriteJSON Failed, CID=%d, err=%s", req.CID, err.Error())
		return err
	}
	if contest.End_time < current {
		logger.Info("contest end")
		return nil
	}
	rdfd := rediscache.GetRedis()
	defer rediscache.CloseRDB(rdfd)
	pubSub, err := rediscache.Subscribe(ctx, rdfd, fmt.Sprintf("%v", req.CID))
	if err != nil {
		logger.Errorf("call rediscache.Subscribe failed,err:%v", err)
		return err
	}
	defer pubSub.Close()
	stop := make(chan struct{})
	subch := rediscache.GetRedisSubCh()
	subch[fmt.Sprintf("%v", req.CID)] = stop
	for {
		select {
		case msg := <-pubSub.Channel():
			newSubmit := dao.Submit{}
			err = rediscache.GetSubscribeMsg(ctx, msg, &newSubmit)
			if err != nil {
				logger.Errorf("call rediscache.GetSubscribeMsg failed,err:%v", err)
			}
			logger.Debugf("newSubmit:%v", utils.Sdump(newSubmit))
			if contest.Ctype == ACM {
				rank := rawRank.(*response.ContestRankRespWithAcm)
				err = RankWithACM(ctx, &rank.Idx, &newSubmit, rank.ProblemIdxMap, rank.UserMap, contest, &rank.Data)
				if err != nil {
					logger.Errorf("call RankWithACM Failed, CID=%d, err=%s", req.CID, err.Error())
					return err
				}
				err = conn.WriteJSON(rank.Data[rank.UserMap[newSubmit.UID]]) //返回submit变化的用户rankitem
				if err != nil {
					logger.Infof("WsConn Close,key:%v", key)
					return err
				}
			} else if contest.Ctype == IOI {
				rank := rawRank.(*response.ConntestRankRespWithIOI)
				RankWithIOI(ctx, &rank.Idx, &newSubmit, rank.ProblemIdxMap, rank.UserMap, contest, &rank.Data)
				if err != nil {
					logger.Errorf("call RankWithIOI Failed, CID=%d, err=%s", req.CID, err.Error())
					return err
				}
				err = conn.WriteJSON(rank.Data[rank.UserMap[newSubmit.UID]]) //返回submit变化的用户rankitem
				if err != nil {
					logger.Infof("WsConn Close,key:%v", key)
					return err
				}
			}
		case <-stop:
			return nil
		}
	}
}
